Passed
Push — master ( efcce7...8e01eb )
by Muhammad Dyas
01:31
created

ActionHandler.getEventPollState   A

Complexity

Conditions 2

Size

Total Lines 7
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 2
1
import {chat_v1 as chatV1} from 'googleapis/build/src/apis/chat/v1';
2
import BaseHandler from './BaseHandler';
3
import NewPollFormCard from '../cards/NewPollFormCard';
4
import {addOptionToState, getStateFromCard} from '../helpers/state';
5
import {callMessageApi} from '../helpers/api';
6
import {createDialogActionResponse, createStatusActionResponse} from '../helpers/response';
7
import PollCard from '../cards/PollCard';
8
import {PollState, Voter, Votes} from '../helpers/interfaces';
9
import AddOptionFormCard from '../cards/AddOptionFormCard';
10
import {saveVotes} from '../helpers/vote';
11
import {MAX_NUM_OF_OPTIONS} from '../config/default';
12
13
/*
14
This list methods are used in the poll chat message
15
 */
16
interface PollAction {
17
  saveOption(): Promise<chatV1.Schema$Message>;
18
19
  getEventPollState(): PollState;
20
}
21
22
export default class ActionHandler extends BaseHandler implements PollAction {
23
  async process(): Promise<chatV1.Schema$Message> {
24
    const action = this.event.common?.invokedFunction;
25
    switch (action) {
26
      case 'start_poll':
27
        return await this.startPoll();
28
      case 'vote':
29
        return this.recordVote();
30
      case 'add_option_form':
31
        return this.addOptionForm();
32
      case 'add_option':
33
        return await this.saveOption();
34
      case 'show_form':
35
        return {
36
          actionResponse: {
37
            type: 'DIALOG',
38
            dialogAction: {
39
              dialog: {
40
                body: new NewPollFormCard({topic: '', choices: []}).create(),
41
              },
42
            },
43
          },
44
        };
45
      default:
46
        return createStatusActionResponse('Unknown action!', 'UNKNOWN');
47
    }
48
  }
49
50
  /**
51
   * Handle the custom start_poll action.
52
   *
53
   * @returns {object} Response to send back to Chat
54
   */
55
  async startPoll() {
56
    // Get the form values
57
    const formValues = this.event.common?.formInputs;
58
    const topic = formValues?.['topic']?.stringInputs?.value?.[0]?.trim() ?? '';
59
    const isAnonymous = formValues?.['is_anonymous']?.stringInputs?.value?.[0] === '1';
60
    const allowAddOption = formValues?.['allow_add_option']?.stringInputs?.value?.[0] === '1';
61
    const choices = [];
62
    const votes: Votes = {};
63
64
    for (let i = 0; i < MAX_NUM_OF_OPTIONS; ++i) {
65
      const choice = formValues?.[`option${i}`]?.stringInputs?.value?.[0]?.trim();
66
      if (choice) {
67
        choices.push(choice);
68
        votes[i] = [];
69
      }
70
    }
71
72
    if (!topic || choices.length === 0) {
73
      // Incomplete form submitted, rerender
74
      const dialog = new NewPollFormCard({
75
        topic,
76
        choices,
77
      }).create();
78
      return {
79
        actionResponse: {
80
          type: 'DIALOG',
81
          dialogAction: {
82
            dialog: {
83
              body: dialog,
84
            },
85
          },
86
        },
87
      };
88
    }
89
    const pollCard = new PollCard({
90
      topic: topic, choiceCreator: undefined,
91
      author: this.event.user,
92
      choices: choices,
93
      votes: votes,
94
      anon: isAnonymous,
95
      optionable: allowAddOption,
96
    }).createCardWithId();
97
    // Valid configuration, make the voting card to display in the space
98
    const message = {
99
      cardsV2: [pollCard],
100
    };
101
    const request = {
102
      parent: this.event.space?.name,
103
      requestBody: message,
104
    };
105
    const apiResponse = await callMessageApi('create', request);
106
    if (apiResponse) {
107
      return createStatusActionResponse('Poll started.', 'OK');
108
    } else {
109
      return createStatusActionResponse('Failed to start poll.', 'UNKNOWN');
110
    }
111
  }
112
113
  /**
114
   * Handle the custom vote action. Updates the state to record
115
   * the user's vote then rerenders the card.
116
   *
117
   * @returns {object} Response to send back to Chat
118
   */
119
  recordVote() {
120
    const parameters = this.event.common?.parameters;
121
    if (!(parameters?.['index'])) {
122
      throw new Error('Index Out of Bounds');
123
    }
124
    const choice = parseInt(parameters['index']);
125
    const userId = this.event.user?.name ?? '';
126
    const userName = this.event.user?.displayName ?? '';
127
    const voter: Voter = {uid: userId, name: userName};
128
    const state = this.getEventPollState();
129
130
    // Add or update the user's selected option
131
    state.votes = saveVotes(choice, voter, state.votes, state.anon);
132
    const card = new PollCard(state);
133
    return {
134
      thread: this.event.message?.thread,
135
      actionResponse: {
136
        type: 'UPDATE_MESSAGE',
137
      },
138
      cardsV2: [card.createCardWithId()],
139
    };
140
  }
141
142
  /**
143
   * Opens and starts a dialog that allows users to add details about a contact.
144
   *
145
   * @returns {object} open a dialog.
146
   */
147
  addOptionForm() {
148
    const state = this.getEventPollState();
149
    const dialog = new AddOptionFormCard(state).create();
150
    return createDialogActionResponse(dialog);
151
  };
152
153
  /**
154
   * Handle add new option input to the poll state
155
   * the user's vote then rerenders the card.
156
   *
157
   * @returns {object} Response to send back to Chat
158
   */
159
  async saveOption(): Promise<chatV1.Schema$Message> {
160
    const userName = this.event.user?.displayName ?? '';
161
    const state = this.getEventPollState();
162
    const formValues = this.event.common?.formInputs;
163
    const optionValue = formValues?.['value']?.stringInputs?.value?.[0]?.trim() || '';
164
    addOptionToState(optionValue, state, userName);
165
166
    const cardMessage = new PollCard(state).createMessage();
167
168
    const request = {
169
      name: this.event.message!.name,
170
      requestBody: cardMessage,
171
      updateMask: 'cardsV2',
172
    };
173
    const apiResponse = await callMessageApi('update', request);
174
    if (apiResponse) {
175
      return createStatusActionResponse('Option is added', 'OK');
176
    } else {
177
      return createStatusActionResponse('Failed to add option.', 'UNKNOWN');
178
    }
179
  }
180
181
  getEventPollState(): PollState {
182
    const state = getStateFromCard(this.event);
183
    if (!state) {
184
      throw new ReferenceError('no valid state in the event');
185
    }
186
    return JSON.parse(state);
187
  }
188
}
189